﻿#include <algorithm>
#include <gtest/gtest.h>
#include <iostream>
#include <vector>

#include <learn/geom/geom_bezier_curve.h>
#include <learn/geom/geom_bezier_surface.h>
#include <learn/geom/geom_bspline_curve.h>
#include <learn/geom/geom_bspline_surface.h>
#include <learn/geom/geom_composed_curve.h>
#include <learn/geom/geom_conic.h>
#include <learn/geom/geom_param_curve.h>
#include <learn/geom/geom_param_surface.h>
#include <learn/geom/geom_trimmed_curve.h>
#include <learn/geom/quat.h>
#include <learn/utils/json.h>
#include <learn/utils/sfinae.h>

using namespace xi;
using namespace xi::geom;
using namespace xi::utils;

TEST(TestGeomDump, vec)
{
    Vec3 v(1.0, 2.0, 3.0);
    auto j = v.dump_json();
    // std::cout << v << std::endl;
    // std::cout << j.dump() << std::endl;

    Mat4x4<double> m(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
    j = m.dump_json();
    // std::cout << m << std::endl;
    // std::cout << j.dump() << std::endl;

    Quat<double> q(1.0, 2.0, 3.0, 4.0);
    j = q.dump_json();
    // std::cout << q << std::endl;
    // std::cout << j.dump() << std::endl;

    j = v;
    // std::cout << j.dump() << std::endl;
}

TEST(TestGeomDump, bezier)
{
    {
        std::vector<Vec3> points = {Vec3{1.0, 2.0, 3.0}, Vec3{4.0, 5.0, 6.0}, Vec3{7.0, 8.0, 9.0},
                                    Vec3{10.0, 11.0, 12.0}};
        auto bez = std::make_shared<Geom_BezierCurve<3>>(std::move(points), 3);
        auto j = bez->dump_json();
        // std::cout << j.dump() << std::endl;
    }

    {
        std::vector<Vec4> points = {Vec4{1.0, 2.0, 3.0, 4.0}, Vec4{5.0, 6.0, 7.0, 8.0}, Vec4{9.0, 10.0, 11.0, 12.0},
                                    Vec4{13.0, 14.0, 15.0, 16.0}};
        auto bez = std::make_shared<Geom_BezierCurve<3, true>>(std::move(points), 3);
        auto j = bez->dump_json();
        // std::cout << j.dump() << std::endl;
    }

    {
        std::vector<Vec3> points = {Vec3{1.0, 2.0, 3.0},    Vec3{4.0, 5.0, 6.0},    Vec3{7.0, 8.0, 9.0},
                                    Vec3{10.0, 11.0, 12.0}, Vec3{13.0, 14.0, 15.0}, Vec3{16.0, 17.0, 18.0},
                                    Vec3{19.0, 20.0, 21.0}, Vec3{22.0, 23.0, 24.0}, Vec3{25.0, 26.0, 27.0},
                                    Vec3{28.0, 29.0, 30.0}, Vec3{31.0, 32.0, 33.0}, Vec3{34.0, 35.0, 36.0}};
        auto bez = std::make_shared<Geom_BezierSurface<3>>(std::move(points), 2, 3);
        auto j = bez->dump_json();
        // std::cout << j.dump() << std::endl;
    }

    {
        std::vector<Vec4> points = {
            Vec4{1.0, 2.0, 3.0, 4.0},     Vec4{5.0, 6.0, 7.0, 8.0},     Vec4{9.0, 10.0, 11.0, 12.0},
            Vec4{13.0, 14.0, 15.0, 16.0}, Vec4{17.0, 18.0, 19.0, 20.0}, Vec4{21.0, 22.0, 23.0, 24.0},
            Vec4{25.0, 26.0, 27.0, 28.0}, Vec4{29.0, 30.0, 31.0, 32.0}, Vec4{33.0, 34.0, 35.0, 36.0},
            Vec4{37.0, 38.0, 39.0, 40.0}, Vec4{41.0, 42.0, 43.0, 44.0}, Vec4{45.0, 46.0, 47.0, 48.0},
            Vec4{49.0, 50.0, 51.0, 52.0}, Vec4{53.0, 54.0, 55.0, 56.0}, Vec4{57.0, 58.0, 59.0, 60.0},
            Vec4{61.0, 62.0, 63.0, 64.0}, Vec4{65.0, 66.0, 67.0, 68.0}, Vec4{69.0, 70.0, 71.0, 72.0},
            Vec4{73.0, 74.0, 75.0, 76.0}, Vec4{77.0, 78.0, 79.0, 80.0}, Vec4{81.0, 82.0, 83.0, 84.0},
            Vec4{85.0, 86.0, 87.0, 88.0}, Vec4{89.0, 90.0, 91.0, 92.0}, Vec4{93.0, 94.0, 95.0, 96.0},
        };
        auto bez = std::make_shared<Geom_BezierSurface<3, true>>(std::move(points), 5, 3);
        auto j = bez->dump_json();
        // std::cout << j.dump() << std::endl;
    }
}

TEST(TestGeomDump, bspline)
{
    {
        std::vector<Vec3> points = {Vec3{1.0, 2.0, 3.0}, Vec3{4.0, 5.0, 6.0}, Vec3{7.0, 8.0, 9.0},
                                    Vec3{10.0, 11.0, 12.0}};
        std::vector<double> knots = {0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0};
        auto bez = std::make_shared<Geom_BSplineCurve<3>>(std::move(points), std::move(knots), 3);
        auto j = bez->dump_json();
        // std::cout << j.dump() << std::endl;
    }

    {
        std::vector<Vec4> points = {Vec4{1.0, 2.0, 3.0, 4.0}, Vec4{5.0, 6.0, 7.0, 8.0}, Vec4{9.0, 10.0, 11.0, 12.0},
                                    Vec4{13.0, 14.0, 15.0, 16.0}};
        std::vector<double> knots = {0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0};
        auto bez = std::make_shared<Geom_BSplineCurve<3, true>>(std::move(points), std::move(knots), 3);
        auto j = bez->dump_json();
        // std::cout << j.dump() << std::endl;
    }

    {
        std::vector<Vec3> points = {Vec3{1.0, 2.0, 3.0},    Vec3{4.0, 5.0, 6.0},    Vec3{7.0, 8.0, 9.0},
                                    Vec3{10.0, 11.0, 12.0}, Vec3{13.0, 14.0, 15.0}, Vec3{16.0, 17.0, 18.0},
                                    Vec3{19.0, 20.0, 21.0}, Vec3{22.0, 23.0, 24.0}, Vec3{25.0, 26.0, 27.0},
                                    Vec3{28.0, 29.0, 30.0}, Vec3{31.0, 32.0, 33.0}, Vec3{34.0, 35.0, 36.0}};
        std::vector<double> uKnots = {0.0, 0.0, 0.0, 1.0, 1.0, 1.0};
        std::vector<double> vKnots = {0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0};
        auto bez =
            std::make_shared<Geom_BSplineSurface<3>>(std::move(points), std::move(uKnots), std::move(vKnots), 2, 3);
        auto j = bez->dump_json();
        // std::cout << j.dump() << std::endl;
    }

    {
        std::vector<Vec4> points = {
            Vec4{1.0, 2.0, 3.0, 4.0},     Vec4{5.0, 6.0, 7.0, 8.0},     Vec4{9.0, 10.0, 11.0, 12.0},
            Vec4{13.0, 14.0, 15.0, 16.0}, Vec4{17.0, 18.0, 19.0, 20.0}, Vec4{21.0, 22.0, 23.0, 24.0},
            Vec4{25.0, 26.0, 27.0, 28.0}, Vec4{29.0, 30.0, 31.0, 32.0}, Vec4{33.0, 34.0, 35.0, 36.0},
            Vec4{37.0, 38.0, 39.0, 40.0}, Vec4{41.0, 42.0, 43.0, 44.0}, Vec4{45.0, 46.0, 47.0, 48.0},
            Vec4{49.0, 50.0, 51.0, 52.0}, Vec4{53.0, 54.0, 55.0, 56.0}, Vec4{57.0, 58.0, 59.0, 60.0},
            Vec4{61.0, 62.0, 63.0, 64.0}, Vec4{65.0, 66.0, 67.0, 68.0}, Vec4{69.0, 70.0, 71.0, 72.0},
            Vec4{73.0, 74.0, 75.0, 76.0}, Vec4{77.0, 78.0, 79.0, 80.0}, Vec4{81.0, 82.0, 83.0, 84.0},
            Vec4{85.0, 86.0, 87.0, 88.0}, Vec4{89.0, 90.0, 91.0, 92.0}, Vec4{93.0, 94.0, 95.0, 96.0},
        };
        std::vector<double> uKnots = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0};
        std::vector<double> vKnots = {0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0};
        auto bez = std::make_shared<Geom_BSplineSurface<3, true>>(std::move(points), std::move(uKnots),
                                                                  std::move(vKnots), 5, 3);
        auto j = bez->dump_json();
        // std::cout << j.dump() << std::endl;
    }
}

TEST(TestGeomDump, composed)
{
    std::vector<Vec3> points1 = {Vec3{1.0, 2.0, 3.0}, Vec3{4.0, 5.0, 6.0}, Vec3{7.0, 8.0, 9.0}, Vec3{10.0, 11.0, 12.0}};
    auto bez = std::make_shared<Geom_BezierCurve<3>>(std::move(points1), 3);

    std::vector<Vec3> points2 = {Vec3{1.0, 2.0, 3.0}, Vec3{4.0, 5.0, 6.0}, Vec3{7.0, 8.0, 9.0}, Vec3{10.0, 11.0, 12.0}};
    std::vector<double> knots = {0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0};
    auto bpsline = std::make_shared<Geom_BSplineCurve<3>>(std::move(points2), std::move(knots), 3);

    auto ellipse = std::make_shared<Geom_Ellipse<3>>(Vec3{0.0, 0.0, 0.0}, Vec3{1.0, 0.0, 0.0}, Vec3{0.0, 1.0, 0.0});
    auto trimmed = std::make_shared<Geom_TrimmedCurve<3>>(ellipse, 0.2, 0.8);

    auto global2Local = [](double global) -> std::tuple<std::size_t, double> {
        if (global < 0.2)
            return std::make_tuple(0, 0.0);
        else if (global < 0.8)
            return std::make_tuple(1, global - 0.2);
        else
            return std::make_tuple(2, 1.0);
    };

    auto composed = std::make_shared<Geom_ComposedCurve<3>>(
        global2Local, std::vector<std::shared_ptr<Geom_Curve<3>>>{bez, bpsline, trimmed}, 0, 1);
    auto j = composed->dump_json();
    // std::cout << j.dump() << std::endl;
}

TEST(TestGeomDump, conic)
{
    {
        Geom_Ellipse<3> ellipse(Vec3{0.0, 0.0, 0.0}, Vec3{1.0, 0.0, 0.0}, Vec3{0.0, 1.0, 0.0});
        auto j = ellipse.dump_json();
        std::cout << j.dump() << std::endl;
    }

    {
        Geom_Hyperbola<3> hyperbola(Vec3{0.0, 0.0, 0.0}, Vec3{1.0, 0.0, 0.0}, Vec3{0.0, 1.0, 0.0});
        auto j = hyperbola.dump_json();
        std::cout << j.dump() << std::endl;
    }

    {
        Geom_Parabola<3> parabola(Vec3{0.0, 0.0, 0.0}, Vec3{1.0, 0.0, 0.0}, Vec3{0.0, 1.0, 0.0});
        auto j = parabola.dump_json();
        std::cout << j.dump() << std::endl;
    }
}

TEST(TestGeomDump, param)
{
    {
        auto f = [](double t) -> Vec3 { return Vec3{t, t * t, t * t * t}; };
        auto df = [](double t) -> Vec3 { return Vec3{1.0, 2.0 * t, 3.0 * t * t}; };
        auto ddf = [](double t) -> Vec3 { return Vec3{0.0, 2.0, 6.0 * t}; };
        Geom_ParamCurve<3> curve(0, 1, f, df, ddf);
        auto j = curve.dump_json();
        std::cout << j.dump() << std::endl;
    }

    {
        auto f = [](double u, double v) -> Vec3 { return Vec3{u, v, u * v}; };
        auto df = [](double u, double v) -> std::array<Vec3, 2> { return {{Vec3{1.0, 0.0, v}, Vec3{0.0, 1.0, u}}}; };
        Geom_ParamSurface<3> surface(0, 1, 0, 1, f, df);
        auto j = surface.dump_json();
        std::cout << j.dump() << std::endl;
    }
}

TEST(TestGeomDump, trimmed)
{
    auto ellipse = std::make_shared<Geom_Ellipse<3>>(Vec3{0.0, 0.0, 0.0}, Vec3{1.0, 0.0, 0.0}, Vec3{0.0, 1.0, 0.0});
    auto trimmed = std::make_shared<Geom_TrimmedCurve<3>>(ellipse, 0.2, 0.8);
    auto j = trimmed->dump_json();
    std::cout << j.dump() << std::endl;
}
